import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.LinkedList;
import java.util.Observable;
import java.util.Observer;
import java.util.Stack;
import java.util.concurrent.PriorityBlockingQueue;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;

/*
 * ****************************
 * Eric Doherty
 * Jesse Kinney
 * 
 * TCSS 422 Scheduler simulator
 * CPU Class
 * 
 * 6/4/08
 * ****************************
 */

public class CPU extends Observable implements Observer
{
  private PriorityBlockingQueue<Interrupt> interrupts;

  private Interrupt curInterrupt;

  private Stack<Interrupt> inService;

  private int timeToRun;

  private int timeCounter;

  private Scheduler scheduler;

  private Timer timer;

  private boolean keysOK;

  private JLabel processType;

  public CPU(LinkedList<Process> processes, int timeToRun)
  {
    scheduler = new Scheduler(processes);
    addObserver(scheduler);
    interrupts = new PriorityBlockingQueue<Interrupt>();
    inService = new Stack<Interrupt>();
    this.timeToRun = timeToRun;
    timeCounter = 0;
    scheduler.schedule();
    keysOK = false;
    processType = new JLabel("");

    // Used to register keyboard events.
    JFrame frame = new JFrame();
    frame.add(processType);
    frame.setPreferredSize(new Dimension(180, 50));
    frame.setLocation(600, 250);
    frame.pack();
    frame.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.addKeyListener(new KeyAdapter()
    {
      public void keyPressed(KeyEvent e)
      {
        if (keysOK)
        {
          if (scheduler.getRunning() != null)
          {
            scheduler.getRunning().systemCall();
          }
        }
      }
    });

    timer = new Timer(2000, new ActionListener()
    {
      public void actionPerformed(ActionEvent evt)
      {
        timeCounter++;
        System.out.println("Clock Interrupt");
        interrupts.add(new Interrupt(0));
        serviceInterrupts();
      }
    });

    timer.setRepeats(true);
    timer.start();
  }

  public void loop()
  {
    while (timeCounter < timeToRun)
    {
      if (scheduler.getRunning() != null)
      {
        processType.setText(scheduler.getRunning().getClass()
            .getSimpleName());
      }

      if (scheduler.getRunning() != null)
      {
        if (scheduler.getRunning().getClass().getSimpleName().equals(
            "InteractiveProcess"))
        {
          keysOK = true;
        }
        else
        {
          keysOK = false;
        }
      }

      if (scheduler.getRunning() != null)
      {
        scheduler.getRunning().incrementPC();
      }

      if (scheduler.getRunning() != null)
      {
        scheduler.getRunning().doInstruction();
      }
    }

    timer.stop();
  }

  // Busy waits and continually checks if a higher priority interrupt was
  // added.
  public void serviceInterrupts()
  {
    if (!interrupts.isEmpty())
    {
      Interrupt servicing = interrupts.poll();

      serviceInterruptshelper(servicing);

      scheduler.switchOutRunning();
      scheduler.schedule();
    }
  }

  private void serviceInterruptshelper(Interrupt interrupt)
  {
    System.out.println("Servicing Interrupt");

    curInterrupt = interrupt;
    int startLength = interrupts.size();

    for (int i = 0; i < curInterrupt.getTimeToProcess(); i++)
    {
      if (startLength < interrupts.size())
      {
        // switch out current interrupt being processed with new higher
        // priority interrupt
        if (interrupts.peek().getPriority() < curInterrupt.getPriority())
        {
          inService.push(curInterrupt);
          curInterrupt = interrupts.poll();
          i = 0;
          System.out
              .println("Running process interrupted by higher priority interrupt");
        }
      }
    }

    if (curInterrupt.getEventType() != null)
    {
      setChanged();
      notifyObservers(new Event(curInterrupt.getEventType()));
    }

    // Process interrupted interrupts
    if (!inService.isEmpty())
    {
      serviceInterruptshelper(inService.pop());
    }
  }

  // Adds the interrupt generated from the ending of an IO event.
  public void update(Observable o, Object obj)
  {
    Event event = (Event) obj;

    if (event.getData() != null)
    {
      interrupts.add(event.getData());
    }
    serviceInterrupts();
  }
}
